package org.leumasjaffe.io;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
 * Class .IPAddress in file IPAddress.java <br>
 * 
 * @author leumasjaffe
 * Created on: Aug 18, 2012
 * 
 */
@NonNullByDefault
public final class IPAddress {
	
	private IPAddress() {}
	
	/**
	 * Determines if a String is a 'valid' IPv4 Address <br>
	 * Does not verify if a connection can be made <br>
	 * Note, an IPv4 Address is of the form xxx.xxx.xxx.xxx:xxxxx 
	 * where xxx is between 0 and 255 (8-bit), and xxxxx is between 0 and 65535 (16-bit)
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv4 Address
	 */
	public static boolean ipv4(String address) {
		if (address.matches("(\\d{1,3}\\.){3}\\d{1,3}(:\\d{1,5})?")) {
			String[] parts = address.split("\\.");
			String[] ports = parts[3].split(":");
			parts[3] = ports[0];
			for (int i = 0; i < 4; i++) {
				int j = Integer.decode(parts[i]);
				if (j >= 256)
					return false;
			}
			if (ports.length == 2) {
				int i = Integer.decode(ports[1]);
				if (i >= 65536)
					return false;
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Determines if a String is a 'valid' IPv6 Address <br>
	 * Does not verify if a connection can be made <br>
	 * Note, an IPv6 Address is of the form [xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:ddddd 
	 * where xxxx is a hexadecimal number between 0 and FFFF (0 and 65535 (16-bit)), and 
	 * ddddd is a decimal number between 0 and 65535 <br>
	 * Note: if there is no port, the brackets may not be included, and the first string of zero fields is
	 * reduced (ex: 1111:1111:0000:0000:1111:0000:1111:1111 is equal to 1111:1111::1111:0000:1111:1111);
	 * localhost (127.0.0.1) is written "::1"
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv6 Address
	 */
	@SuppressWarnings("null")
	public static boolean ipv6(String address) {
		String addr = address;
		if (addr.startsWith("[")) {
			int i = addr.indexOf(']');
			if (i != -1) {
				String ess = addr.substring(i);
				addr = addr.substring(1, i);
				if (ess.length() > 1) {
					if (ess.matches("\\](:\\d{1,5})?") && Integer.decode(ess.substring(2)) < 65536)
						return ipv6(address.substring(1, i));
					else return false;
				}
			}
			else
				return false;
		}
		return expand_ipv6(addr).matches("([\\da-f]{1,4}:){7}[\\da-f]{1,4}");
	}
	
	@SuppressWarnings("null")
	private static String expand_ipv6(String address) {
		String addr = new String(address);
		if (addr.contains("::")) {
			if (addr.charAt(0) == ':')
				addr = "0" + addr;
			int i = addr.split(":").length;
			String insert = ":0";
			while (++i < 9) insert += ":0";
			addr = addr.replace("::", insert + ":");
		}
		for (int i = 0; i < 8; i++) {
			int b = 5*i;
			String end = addr.substring(b);
			int e = end.indexOf(':');
			if (e == -1)
				e = end.length();
			e -= b;
			addr = addr.substring(0, b);
			while (e++ < 4)
				addr += "0";
			addr += end;
		}
		return addr;
	}

//	@SuppressWarnings("null")
//	public static boolean ipv6_block(String address) {
//		int i = address.indexOf('/');
//		if (i == -1) return false;
//		String end = address.substring(i);
//		if (end.matches("/\\d")) {
//			return ipv6(address.substring(0, i));
//		}
//		return false;
//	}
//	
//	public static String ipv6_block_end(String address) {
//		if (ipv6_block(address)) {
//			int i = Integer.valueOf(address.substring(address.indexOf('/'))), j = i/16, k = i%16;
//			String addr = expand_ipv6(address);
//			String output = null;
//			for (int m = 0; m < 40; m+=5) {
//				int part = (m <= j) ? (Integer.decode("0x" + addr.substring(m, m+4)) | 
//						((m == j) ? (0xFFFF >>> k) : 0x0000)) : 0xFFFF;
//				
//				if (m == 0)
//					output = Integer.toHexString(part);
//				else
//					output += ":" + Integer.toHexString(part);
//			}
//			if (output != null)
//				return output;
//		}
//		throw new IllegalArgumentException("This is not a block address");
//	}

	/**
	 * Determines if a String is either a valid IPv4 or IPv6 Address
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv4 or IPv6 Address
	 */
	public static boolean ipAddr(String address) {
		return ipv4(address) || ipv6(address);
	}
	
	/**
	 * Determines if a String is a 'valid' IPv4 Address <br>
	 * Does not verify if a connection can be made <br>
	 * Note, an IPv4 Address is of the form xxx.xxx.xxx.xxx:xxxxx 
	 * where xxx is between 0 and 255 (8-bit), and xxxxx is between 0 and 65535 (16-bit)
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv4 Address, allowing for http(s):// and page values
	 */
	public static boolean inet_ipv4(String address) {
		return address.matches("(https?://)?(\\d{1,3}\\.){3}\\d{1,3}(:\\d{1,5})?/?.*");
	}
	
	/**
	 * Determines if a String is a 'valid' IPv6 Address <br>
	 * Does not verify if a connection can be made <br>
	 * Note, an IPv6 Address is of the form [xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:ddddd 
	 * where xxxx is a hexadecimal number between 0 and ffff (0 and 65535 (16-bit)), and 
	 * ddddd is a decimal number between 0 and 65535 <br>
	 * Note: if there is no port, the brackets may not be included, also, one set of zero fields may be
	 * reduced (the longest set, leftmost as tie-break) (ex: 1111:1111:0000:0000:1111:0000:1111:1111 is equal 
	 * to 1111:1111::1111:0000:1111:1111); localhost (127.0.0.1) is written "::1"
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv6 Address, allowing for http(s):// and page values
	 */
	@SuppressWarnings("null")
	public static boolean inet_ipv6(String address) {
		return ipv6(address.replaceFirst("https?://", "").replaceFirst("/.*", ""));
	}
	
	/**
	 * Determines if a String is either a valid IPv4 or IPv6 Address
	 * @param address A String that may or may not be an IP Address
	 * @return Returns true if the given string is an IPv4 or IPv6 Address,
	 *  allowing for http(s):// and page values
	 */
	public static boolean inet_ipAddr(String address) {
		return inet_ipv4(address) || inet_ipv6(address);
	}

}
